/*
* This file is a part of the open-eBackup project.
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file, You can obtain one at
* http://mozilla.org/MPL/2.0/.
*
* Copyright (c) [2024] Huawei Technologies Co.,Ltd.
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
*/
#include "BackintConst.h"
#include <string>
#include <cstdio>
#include <iostream>
#include <vector>
#include <cstring>
#include <fstream>
#include <dirent.h>
#include <sstream>
#include <unistd.h>
#include <securec.h>
#include "json/json.h"
#include "common/File.h"
#include "common/MpString.h"

namespace backint {
const std::string DB_INFO_FILE = "dbinfo.txt";
const uint32_t PIPE_OR_FILE_INDEX = 1;
const uint32_t INPUT_FILE_INDEX = 1;
const uint32_t EBID_INPUT_FILE_INDEX = 2;
const uint32_t DB_NAME_INDEX = 1;
const uint32_t MIN_INPUT_FILE_LINES = 2;
const uint32_t INPUT_FILE_TITLE_LINE = 0;
const uint32_t VERSION_INDEX = 2;
const uint32_t NAME_PATH_MAX = 512;

// 去除前后引号和空格
void MoveQuote(std::string &data)
{
    if (data.empty()) {
        return;
    }

    if (data[0] == '\"') {
        data = data.substr(1, data.size() - 1);
    }
    if (data.size() >= CONSTANTS_2 && data[data.size() - 1] == '\"' && data[data.size() - 1] != '\\') {
        data = data.substr(0, data.size() - 1);
    }
}

// 去除前后空格
void Simplefied(std::string &data)
{
    std::string blanks(" ");
    data.erase(0, data.find_first_not_of(blanks));
    data.erase(data.find_last_not_of(blanks) + 1);
}

bool IsNumber(const std::string &s)
{
    std::stringstream ss(s);
    double d;
    return (ss >> d) && (ss >> std::ws).eof();
}

int CreateDirs(const std::string& path)
{
    if (Module::CFile::DirExist(path.c_str())) {
        return SUCCESS;
    }
    char dir[256] = {0};
    char *p = nullptr;
    int ret = snprintf_s(dir, sizeof(dir), sizeof(dir) - 1, "%s", path.c_str());
    if (ret <= 0) {
        return FAILED;
    }
    size_t len = strlen(dir);
    if (dir[len - 1] == '/') {
        dir[len - 1] = 0;
    }
    for (p = dir + 1; *p; p++) {
        if (*p == '/') {
            *p = 0;
            if (Module::CFile::DirExist(dir)) {
                *p = '/';
                continue;
            }
            if (Module::CFile::CreateDir(dir) != SUCCESS) {
                return FAILED;
            }
            *p = '/';
        }
    }
#ifdef WIN32
    COMMLOG(OS_LOG_ERROR, "Windows does not support create folder.");
    return FAILED;
#else
    if (mkdir(dir, S_IRWXU) != 0 && errno != EEXIST) {
        return FAILED;
    }
#endif
    return SUCCESS;
}

int GetFileInfo(const std::string &data, StuFileData& stuFileData)
{
    LOGGUARD("");
    std::string inputData = data;
    int startIndex;
    int endIndex;
    int dataLength = data.size();

    startIndex = inputData.find("\"");
    if (startIndex == std::string::npos) {
        ERRLOG("Failed to get file info");
        return FAILED;
    }
    inputData = inputData.substr(startIndex + 1);
    endIndex = inputData.find("\"");
    if (endIndex == std::string::npos) {
        ERRLOG("Failed to get file info");
        return FAILED;
    }
    stuFileData.m_filePath = data.substr(startIndex + 1, endIndex);

    if (endIndex + 1 != dataLength) {
        inputData = inputData.substr(endIndex + 1);
        Simplefied(inputData);
        if (IsNumber(inputData) == 1) {
            stuFileData.m_dataLength = atoi(inputData.c_str());
        }
    }
    INFOLOG("The file info is stuFileData.m_filePath: %s, stuFileData.m_dataLength: %d", \
            stuFileData.m_filePath.c_str(), stuFileData.m_dataLength);
    return SUCCESS;
}

int GetBackupInfo(const std::string input, StuEBIDData& stuEBIDInfo)
{
    std::vector<std::string> vecBackupInfo;
    std::string data = input;
    int startIndex;
    int endIndex;
    int dataLength = data.size();
    int pos = 0;

    std::vector<std::string> pipeContent;
    Module::CMpString::StrSplit(pipeContent, data, ' ');
    stuEBIDInfo.m_backupID = pipeContent[CONSTANTS_1];
    if (pipeContent[CONSTANTS_2][0] == '\"') {
        stuEBIDInfo.m_filePath = pipeContent[CONSTANTS_2].substr(1, pipeContent[CONSTANTS_2].size() - CONSTANTS_2);
    } else {
        stuEBIDInfo.m_filePath = pipeContent[CONSTANTS_2];
    }

    if (pipeContent.size() == CONSTANTS_4) {
        if (pipeContent[CONSTANTS_3][0] == '\"') {
            stuEBIDInfo.m_destPath = pipeContent[CONSTANTS_3].substr(1, pipeContent[CONSTANTS_3].size() - CONSTANTS_2);
        } else {
            stuEBIDInfo.m_destPath = pipeContent[CONSTANTS_3];
        }
    }
    INFOLOG("BackupID:[%s], fileName:[%s] m_destName:[%s]", stuEBIDInfo.m_backupID.c_str(), \
        stuEBIDInfo.m_filePath.c_str(), stuEBIDInfo.m_destPath.c_str());
    return SUCCESS;
}

int ReadPipeInfo(const std::string &filePath, StuFileInfo &stFileInfo)
{
    LOGGUARD("");
    std::ifstream pipe(filePath);
    if (!pipe.is_open()) {
        ERRLOG("file open failed");
        return FAILED;
    }
    std::string data;

    while (std::getline(pipe, data)) {
        std::string strData = data;
        if (strData.find("#PIPE") == 0 || strData.find("#FILE") == 0 || strData.find("#NULL") == 0) {
            StuFileData stuFileData;
            if (GetFileInfo(data, stuFileData) != SUCCESS) {
                return FAILED;
            }
            stFileInfo.fileData.push_back(stuFileData);
        } else if (strData.find("#SOFTWAREID") == 0) {
            stFileInfo.m_inputFileTitle = data;
        } else if (strData.find("#EBID") == 0) {
            StuEBIDData eBIDInfo;
            if (GetBackupInfo(data, eBIDInfo) != SUCCESS) {
                return FAILED;
            }
            stFileInfo.m_eBIDData.push_back(eBIDInfo);
        } else {
            continue;
        }
    }
    return SUCCESS;
}

std::vector<std::string> GetFiles(const std::string &strCurrentDir)
{
    std::vector<std::string> vFiles;
    DIR *dir;
    struct dirent *pDir;

    if ((dir = opendir(strCurrentDir.c_str())) == nullptr) {
        ERRLOG("Failed to open dir: %s", strCurrentDir.c_str());
        return vFiles;
    }

    while ((pDir = readdir(dir)) != nullptr) {
        if (strcmp(pDir->d_name, ".") == 0 || strcmp(pDir->d_name, "..") == 0 ||
            strcmp(pDir->d_name, ".snapshot") == 0) {
            continue;
        } else if (pDir->d_type == DT_REG) {
            vFiles.push_back(strCurrentDir + "/" + pDir->d_name);
        } else if (pDir->d_type == DT_LNK) {
            continue;
        } else if (pDir->d_type == DT_DIR) {
            std::string strNextDir = strCurrentDir + "/" + pDir->d_name;
            std::vector<std::string> ss = GetFiles(strNextDir);
            vFiles.insert(vFiles.end(), ss.begin(), ss.end());
        }
    }
    closedir(dir);
    return vFiles;
}

std::string GetLogStoragePath(const std::string &parentPath, const std::string &dbName)
{
    std::string pathInfo;
    std::vector<std::string> files = GetFiles(parentPath);
    std::ifstream infile;
    std::string data;
    for (size_t i = 0; i < files.size(); ++i) {
        if (files[i].find(DB_INFO_FILE) == std::string::npos) {
            continue;
        }
        infile.open(files[i]);
        infile >> data;
        if (dbName.compare(data) == 0) {
            INFOLOG("Succeed to find path");
            infile.close();
            int pos = files[i].find_last_of("/");
            pathInfo = files[i].substr(0, pos);
            return pathInfo;
        }
        infile.close();
    }
    ERRLOG("Failed to find path");
    return pathInfo;
}

uint32_t WriteResultToFile(const Json::Value &jsonValue, const std::string &outFile)
{
    Json::StyledWriter sw;
    std::ofstream output;
    output.open(outFile, std::ios::out | std::ios::app);
    if (!output.is_open()) {
        ERRLOG("Failed to open file:%s, errno:%d", outFile.c_str(), errno);
        return FAILED;
    }
    output << sw.write(jsonValue);
    output.close();
    return SUCCESS;
}

uint32_t ParsePluginParams(const std::string &paramFile, PlugParams &plugParams)
{
    LOGGUARD("");
    Json::Value jsonValue;
    std::string paramString;

    if (Module::CFile::ReadFile(paramFile, paramString) != SUCCESS) {
        ERRLOG("Failed to open file:%s", paramFile.c_str());
        return FAILED;
    }
    if (!Module::JsonHelper::JsonStringToJsonValue(paramString, jsonValue)) {
        ERRLOG("Failed to convert string to json");
        return FAILED;
    }
    plugParams.m_pid = jsonValue["pid"].asString();
    plugParams.m_jobId = jsonValue["job_id"].asString();
    plugParams.m_subJobId = jsonValue["sub_job_id"].asString();
    plugParams.m_dbName = jsonValue["db_name"].asString();
    plugParams.m_dataPath = jsonValue["data_path"].asString();
    plugParams.m_cachePath = jsonValue["cache_path"].asString();
    plugParams.m_metaPath = jsonValue["meta_path"].asString();
    return SUCCESS;
}

uint32_t GetInFilePath(int argc, char** argv, std::string &filePath)
{
    std::string regs = "-i";
    for (auto i = 0; i < argc; ++i) {
        if (regs.compare(argv[i]) == SUCCESS && (argc > i + 1)) {
            filePath = argv[i + 1];
            return SUCCESS;
        }
    }
    ERRLOG("Failed to get infile path.");
    return FAILED;
}

uint32_t GetVersionFromInfile(const std::string &filePath, std::string &version)
{
    std::vector<std::string> fileContent;
    std::string inFilePath = filePath;
    if (Module::CIPCFile::ReadFile(inFilePath, fileContent) != SUCCESS) {
        ERRLOG("Failed to read input file.");
        return FAILED;
    }
    if (fileContent.size() < MIN_INPUT_FILE_LINES) {
        ERRLOG("Input file content is incomplete.");
        return FAILED;
    }
    std::vector<std::string> vecFirstLine;
    Module::CMpString::StrSplit(vecFirstLine, fileContent[INPUT_FILE_TITLE_LINE], ' ');
    if (vecFirstLine.size() <= VERSION_INDEX) {
        ERRLOG("The input file title is invalid.");
        return FAILED;
    }
    std::string versionInfo = vecFirstLine[VERSION_INDEX];
    size_t pos = versionInfo.find("\"");
    if (pos != std::string::npos) {
        version = versionInfo.substr(0, pos - 1); // 只获取大版本号，如：1.5 1.0
        return SUCCESS;
    }
    ERRLOG("Failed to get version from infile.");
    return FAILED;
}

uint32_t GetDbNameFromInfile(std::string &filePath, std::string &dBName)
{
    std::vector<std::string> fileContent;
    if (Module::CFile::ReadFileWithArray(filePath, fileContent) != SUCCESS) {
        ERRLOG("Read input file failed.");
        return FAILED;
    }
    if (fileContent.size() < MIN_INPUT_FILE_LINES) {
        ERRLOG("Input file content is incomplete.");
        return FAILED;
    }
    std::vector<std::string> pipeContent;
    std::vector<std::string> inFileContent;
    if (fileContent[PIPE_OR_FILE_INDEX].find("EBID") != std::string::npos) {
        Module::CMpString::StrSplit(pipeContent, fileContent[PIPE_OR_FILE_INDEX], ' ');
        Module::CMpString::StrSplit(inFileContent, pipeContent[EBID_INPUT_FILE_INDEX], '/');
    } else {
        Module::CMpString::StrSplit(pipeContent, fileContent[PIPE_OR_FILE_INDEX], ' ');
        Module::CMpString::StrSplit(inFileContent, pipeContent[INPUT_FILE_INDEX], '/');
    }
    reverse(inFileContent.begin(), inFileContent.end());
    dBName = inFileContent[DB_NAME_INDEX];
    INFOLOG("dBName: %s", dBName.c_str());
    return SUCCESS;
}

uint32_t WriteOutFile(const std::vector<std::string> &result, const std::string &outFile)
{
    std::ofstream outfile(outFile, std::ios::app);
    if (!outfile.is_open()) {
        ERRLOG("Failed to open file: %s, errno: %d.", outFile.c_str(), errno);
        return FAILED;
    }
    for (const auto& line : result) {
        outfile << line << " ";
    }
    outfile << "\n";
    outfile.close();
    return SUCCESS;
}

std::string GetFileName(const std::string &filePath)
{
    size_t pos = filePath.find_last_of('/');
    if (pos == std::string::npos) {
        return filePath;
    }
    return filePath.substr(pos + 1);
}

uint32_t GetLogParentPath(const std::string &filePath, std::string &parentPath)
{
    LOGGUARD("");
    Json::Value jsonValue;
    std::string paramString;

    if (Module::CFile::ReadFile(filePath, paramString) != SUCCESS) {
        ERRLOG("Failed to open file:%s.", filePath.c_str());
        return FAILED;
    }
    INFOLOG("paramString:%s.", paramString.c_str());
    if (!Module::JsonHelper::JsonStringToJsonValue(paramString, jsonValue)) {
        ERRLOG("Failed to convert string to json.");
        return FAILED;
    }
    parentPath = jsonValue["logBackupPath"].asString();
    return SUCCESS;
}

std::string GetCataLogStorePath(const std::string &filePath, std::string &cachePath)
{
    LOGGUARD("");
    std::string tmpPath = filePath;
    std::string dbName;
    if (GetDbNameFromInfile(tmpPath, dbName) != SUCCESS) {
        ERRLOG("Get db name failed.");
        return "";
    }
    std::string retPath = cachePath + "/" + dbName + ".txt";
    INFOLOG("Get ret path: %s.", retPath.c_str());
    std::vector<std::string> fileContent;
    if (Module::CFile::ReadFileWithArray(retPath, fileContent) != SUCCESS) {
        ERRLOG("Read input file failed.");
        return "";
    }
    int lens = fileContent.size();
    std::string ret = fileContent[lens - 1].substr(0, fileContent[lens - 1].size() - 1);
    return ret;
}

std::string GetExeCurrentPath()
{
    char tempPath[NAME_PATH_MAX];
    auto count = readlink("/proc/self/exe", tempPath, NAME_PATH_MAX);
    std::string currentPath = "";
    if (count == -1) {
        ERRLOG("Failed to get link");
        return "";
    }
    tempPath[count] = 0;
    currentPath = tempPath;
    std::string firstParentPath = currentPath.substr(0, currentPath.find_last_of('/'));
    std::string secondParentPath = firstParentPath.substr(0, firstParentPath.find_last_of('/'));
    return secondParentPath;
}

std::string GetCatalogParentPath(const StuParam &inParam, std::string &mDataPath)
{
    bool bDataRepoPath = false;
    std::string logParentPath;
    std::string parFile = GetExeCurrentPath() + "/hdbconfig/backint.conf";
    if (!Module::CFile::FileExist(parFile.c_str())) {
        bDataRepoPath = true;
        logParentPath = mDataPath;
    } else {
        std::string logTargetDir;
        if (GetLogParentPath(parFile, logTargetDir) != SUCCESS) {
            bDataRepoPath = true;
            logParentPath = mDataPath;
        } else {
            logParentPath = logTargetDir;
        }
        std::string dbName;
        std::string filePh = inParam.m_inputFile;
        if (GetDbNameFromInfile(filePh, dbName) != SUCCESS) {
            ERRLOG("Get db name failed.");
            return "";
        }
        if (!bDataRepoPath) {
            logParentPath += "/" + dbName;
        }
        DBGLOG("logParentPath:%s.", logParentPath.c_str());
    }
    std::string parentPath = logParentPath + "/" + inParam.m_dtBackupId;
    return parentPath;
}
}